using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Reflection;

namespace SBPweb.Logging
{
    /// <summary>
    /// Defines the method of archiving.
    /// </summary>
    public enum ArchivingMethod : int
    {
        /// <summary>
        /// No archiving will be done.
        /// </summary>
        None = 0,
        /// <summary>
        /// The old files will be moved to ArchiveFolder.
        /// </summary>
        Move,
        /// <summary>
        /// The old files will be compressed one by one into ArchiveFolder.
        /// </summary>
        Compress,
        /// <summary>
        /// The old files will be compressed togedther into ArchiveFolder.
        /// </summary>
        CompressTogether
    }

    /// <summary>
    /// Defines named archiving intervals.
    /// </summary>
    public static class NamedArchivingIntervals
    {
        /// <summary>
        /// Timespan that represents one day.
        /// </summary>
        public static readonly TimeSpan OneDay = new TimeSpan(1, 0, 0, 0);
        /// <summary>
        /// Timespan that represents one week.
        /// </summary>
        public static readonly TimeSpan OneWeek = new TimeSpan(7, 0, 0, 0);
    }

    /// <summary>
    /// Delegate type for supporting custom events on scheduled archiving.
    /// </summary>
    /// <param name="LogFolder">LogFolder"/> instance that fires the event.</param>
    /// <param name="FilesToArchive">Collection of files to be archived.</param>
    public delegate void ArchivingEventHandler(LogFolder LogFolder, ICollection<String> FilesToArchive);

    /// <summary>
    /// Defines a log folder. Provides auxiliary services like scheduled archiving.
    /// </summary>
    /// <remarks>
    /// Usually there is a one to one relation between file based trace listeners (see EntLibFileTraceListener"/>) and instances of this class.
    /// In a normal scenario, the user of this system instantiates this class using configuration attributes attached to a given file based custom trace listener
    /// by calling LogManager.Initialize. However there are several different constructors present for manual instantiation.
    /// </remarks>
    public sealed class LogFolder
    {
        private String mFolder;
        private String mArchiveFolder;

        private ArchivingMethod mArchivingMethod;

        private TimeSpan mMaxAllowedAge, mAutoArchivingInterval;

        private System.Threading.Timer mTimer;

        /// <summary>
        /// Assign it to provide custom events those will be fired before archiving.
        /// </summary>
        public event ArchivingEventHandler BeforeArchiving;
        /// <summary>
        /// Assign it to provide custom events those will be fired after archiving.
        /// </summary>
        public event ArchivingEventHandler AfterArchiving;

        /// <summary>
        /// The folder assigned to this LogFolder"/> instance.
        /// </summary>
        public String Folder
        {
            get
            {
                return mFolder;
            }
        }

        /// <summary>
        /// The archive folder assigned to this LogFolder"/> instance.
        /// </summary>
        public String ArchiveFolder
        {
            get
            {
                return mArchiveFolder;
            }
        }

        /// <summary>
        /// The archiving method.
        /// </summary>
        public ArchivingMethod ArchivingMethod
        {
            get
            {
                return mArchivingMethod;
            }
        }

        /// <summary>
        /// The maximum timespan before a file will be recognized as archivable on archiving.
        /// </summary>
        public TimeSpan MaxAllowedAge
        {
            get
            {
                return mMaxAllowedAge;
            }
        }

        /// <summary>
        /// The timespan between two archiving.
        /// </summary>
        public TimeSpan AutoArchivingInterval
        {
            get
            {
                return mAutoArchivingInterval;
            }
        }

        /// <summary>
        /// Instantiates a new LogFolder"/>.
        /// </summary>
        /// <param name="Folder">Name of a folder to be the log folder. Must be an existing folder. Can be relative to the Logmanager.BaseFolder or absolute.</param>
        /// <exception cref="System.ArgumentException">Folder does not exists.</exception>
        public LogFolder(String Folder)
            : this(Folder, null, ArchivingMethod.None, NamedArchivingIntervals.OneWeek, NamedArchivingIntervals.OneDay, false)
        {
        }

        /// <summary>
        /// Instantiates a new LogFolder"/>.
        /// </summary>
        /// <param name="Folder">Name of a folder to be the log folder. Must be an existing folder. Can be relative to the Logmanager.BaseFolder or absolute.</param>
        /// <param name="MaxAllowedAge">The maximum age allowed before a log file may be archived.</param>
        /// <exception cref="System.ArgumentException">Folder does not exists.</exception>
        public LogFolder(String Folder, TimeSpan MaxAllowedAge)
            : this(Folder, null, ArchivingMethod.None, MaxAllowedAge, NamedArchivingIntervals.OneDay, false)
        {
        }

        /// <summary>
        /// Instantiates a new LogFolder"/>.
        /// </summary>
        /// <param name="Folder">Name of a folder to be the log folder. Must be an existing folder. Can be relative to the Logmanager.BaseFolder or absolute.</param>
        /// <param name="Seconds">The interval of scheduled archiving.</param>
        /// <exception cref="System.ArgumentException">Folder does not exists.</exception>
        public LogFolder(String Folder, long Seconds)
			: this(Folder, null, ArchivingMethod.None, new TimeSpan(Seconds * 10000000L), NamedArchivingIntervals.OneDay, false)
        {
        }

        /// <summary>
        /// Instantiates a new LogFolder"/>.
        /// </summary>
        /// <param name="Folder">Name of a folder to be the log folder. Must be an existing folder. Can be relative to the Logmanager.BaseFolder or absolute.</param>
        /// <param name="ArchiveFolder">Name of the archive folder. Can be relative to the Logmanager.BaseFolder or absolute.</param>
        /// <exception cref="System.ArgumentException">Folder or ArchiveFolder does not exists.</exception>
        public LogFolder(String Folder, String ArchiveFolder)
			: this(Folder, ArchiveFolder, ArchivingMethod.Move, NamedArchivingIntervals.OneWeek, NamedArchivingIntervals.OneDay, false)
        {
        }

        /// <summary>
        /// Instantiates a new LogFolder"/>.
        /// </summary>
        /// <param name="Folder">Name of a folder to be the log folder. Must be an existing folder. Can be relative to the Logmanager.BaseFolder or absolute.</param>
        /// <param name="ArchiveFolder">Name of the archive folder. Can be relative to the Logmanager.BaseFolder or absolute.</param>
        /// <param name="MaxAllowedAge">The maximum age allowed before a log file may be archived.</param>
        /// <exception cref="System.ArgumentException">Folder or ArchiveFolder does not exists.</exception>
        public LogFolder(String Folder, String ArchiveFolder, TimeSpan MaxAllowedAge)
			: this(Folder, ArchiveFolder, ArchivingMethod.Move, MaxAllowedAge, NamedArchivingIntervals.OneDay, false)
        {
        }

        /// <summary>
        /// Instantiates a new LogFolder"/>.
        /// </summary>
        /// <param name="Folder">Name of a folder to be the log folder. Must be an existing folder. Can be relative to the Logmanager.BaseFolder or absolute.</param>
        /// <param name="ArchiveFolder">Name of the archive folder. Can be relative to the Logmanager.BaseFolder or absolute.</param>
        /// <param name="Seconds">The maximum age allowed before a log file may be archived.</param>
        /// <exception cref="System.ArgumentException">Folder or ArchiveFolder does not exists.</exception>
        public LogFolder(String Folder, String ArchiveFolder, long Seconds)
			: this(Folder, ArchiveFolder, ArchivingMethod.Move, new TimeSpan(Seconds * 10000000L), NamedArchivingIntervals.OneDay, false)
        {
        }

        /// <summary>
        /// Instantiates a new LogFolder"/>.
        /// </summary>
        /// <param name="Folder">Name of a folder to be the log folder. Must be an existing folder. Can be relative to the Logmanager.BaseFolder or absolute.</param>
        /// <param name="ArchiveFolder">Name of the archive folder. Can be relative to the Logmanager.BaseFolder or absolute.</param>
        /// <param name="ArchivingMethod">Archiving method.</param>
        /// <exception cref="System.ArgumentException">Folder does not exists or ArchivingMethod is not ArchivingMethod.None"/> and ArchiveFolder does not exists.</exception>
        /// <exception cref="System.ArgumentNullException">ArchivingMethod is not ArchivingMethod.None"/> and ArchiveFolder does not exists.</exception>
        public LogFolder(String Folder, String ArchiveFolder, ArchivingMethod ArchivingMethod)
			: this(Folder, ArchiveFolder, ArchivingMethod, NamedArchivingIntervals.OneWeek, NamedArchivingIntervals.OneDay, false)
        {
        }

        /// <summary>
        /// Instantiates a new LogFolder"/>.
        /// </summary>
        /// <param name="Folder">Name of a folder to be the log folder. Must be an existing folder. Can be relative to the Logmanager.BaseFolder or absolute.</param>
        /// <param name="ArchiveFolder">Name of the archive folder. Can be relative to the Logmanager.BaseFolder or absolute.</param>
        /// <param name="ArchivingMethod">Archiving method.</param>
        /// <param name="MaxAllowedAge">The maximum age allowed before a log file may be archived.</param>
        /// <exception cref="System.ArgumentException">Folder does not exists or ArchivingMethod is not ArchivingMethod.None"/> and ArchiveFolder does not exists.</exception>
        /// <exception cref="System.ArgumentNullException">ArchivingMethod is not ArchivingMethod.None"/> and ArchiveFolder does not exists.</exception>
        public LogFolder(String Folder, String ArchiveFolder, ArchivingMethod ArchivingMethod, TimeSpan MaxAllowedAge)
			: this(Folder, ArchiveFolder, ArchivingMethod, MaxAllowedAge, NamedArchivingIntervals.OneDay, false)
        {
        }

        /// <summary>
        /// Instantiates a new LogFolder"/>.
        /// </summary>
        /// <param name="Folder">Name of a folder to be the log folder. Must be an existing folder. Can be relative to the Logmanager.BaseFolder or absolute.</param>
        /// <param name="ArchiveFolder">Name of the archive folder. Can be relative to the Logmanager.BaseFolder or absolute.</param>
        /// <param name="ArchivingMethod">Archiving method.</param>
        /// <param name="Seconds">The maximum age allowed before a log file may be archived.</param>
        /// <exception cref="System.ArgumentException">Folder does not exists or ArchivingMethod is not ArchivingMethod.None"/> and ArchiveFolder does not exists.</exception>
        /// <exception cref="System.ArgumentNullException">ArchivingMethod is not ArchivingMethod.None"/> and ArchiveFolder does not exists.</exception>
        public LogFolder(String Folder, String ArchiveFolder, ArchivingMethod ArchivingMethod, long Seconds)
			: this(Folder, ArchiveFolder, ArchivingMethod, new TimeSpan(Seconds * 10000000L), NamedArchivingIntervals.OneDay, false)
        {
        }

        /// <summary>
        /// Instantiates a new LogFolder"/>.
        /// </summary>
        /// <param name="Folder">Name of a folder to be the log folder. Must be an existing folder. Can be relative to the Logmanager.BaseFolder or absolute.</param>
        /// <param name="ArchiveFolder">Name of the archive folder. Can be relative to the Logmanager.BaseFolder or absolute.</param>
        /// <param name="MaxAllowedAge">The maximum age allowed before a log file may be archived.</param>
        /// <param name="AutoArchivingInterval">The interval of scheduled archiving.</param>
        /// <exception cref="System.ArgumentException">Folder or ArchiveFolder does not exists.</exception>
        public LogFolder(String Folder, String ArchiveFolder, TimeSpan MaxAllowedAge, TimeSpan AutoArchivingInterval)
			: this(Folder, ArchiveFolder, ArchivingMethod.Move, MaxAllowedAge, AutoArchivingInterval, false)
        {
        }

        /// <summary>
        /// Instantiates a new LogFolder"/>.
        /// </summary>
        /// <param name="Folder">Name of a folder to be the log folder. Must be an existing folder. Can be relative to the Logmanager.BaseFolder or absolute.</param>
        /// <param name="ArchiveFolder">Name of the archive folder. Can be relative to the Logmanager.BaseFolder or absolute.</param>
        /// <param name="Seconds">The maximum age allowed before a log file may be archived.</param>
		/// <param name="AutoArchivingIntervalSeconds">The interval of scheduled archiving.</param>
        /// <exception cref="System.ArgumentException">Folder or ArchiveFolder does not exists.</exception>
        public LogFolder(String Folder, String ArchiveFolder, long Seconds, long AutoArchivingIntervalSeconds)
            : this(Folder, ArchiveFolder, ArchivingMethod.Move, new TimeSpan(Seconds * 10000000L), new TimeSpan(AutoArchivingIntervalSeconds * 10000000L), false)
		{
        }

        /// <summary>
        /// Instantiates a new LogFolder"/>.
        /// </summary>
        /// <param name="Folder">Name of a folder to be the log folder. Must be an existing folder. Can be relative to the Logmanager.BaseFolder or absolute.</param>
        /// <param name="ArchiveFolder">Name of the archive folder. Can be relative to the Logmanager.BaseFolder or absolute.</param>
        /// <param name="ArchivingMethod">Archiving method.</param>
        /// <param name="Seconds">The maximum age allowed before a log file may be archived.</param>
		/// <param name="AutoArchivingIntervalSeconds">The interval of scheduled archiving.</param>
        /// <exception cref="System.ArgumentException">Folder does not exists or ArchivingMethod is not ArchivingMethod.None"/> and ArchiveFolder does not exists.</exception>
        /// <exception cref="System.ArgumentNullException">ArchivingMethod is not ArchivingMethod.None"/> and ArchiveFolder does not exists.</exception>
        public LogFolder(String Folder, String ArchiveFolder, ArchivingMethod ArchivingMethod, long Seconds, long AutoArchivingIntervalSeconds)
            : this(Folder, ArchiveFolder, ArchivingMethod, new TimeSpan(Seconds * 10000000L), new TimeSpan(AutoArchivingIntervalSeconds * 10000000L), false)
        {
        }

        /// <summary>
        /// Instantiates a new LogFolder"/>.
        /// </summary>
        /// <param name="Folder">Name of a folder to be the log folder. Must be an existing folder. Can be relative to the Logmanager.BaseFolder or absolute.</param>
        /// <param name="ArchiveFolder">Name of the archive folder. Can be relative to the Logmanager.BaseFolder or absolute.</param>
        /// <param name="ArchivingMethod">Archiving method.</param>
        /// <param name="MaxAllowedAge">The maximum age allowed before a log file may be archived.</param>
        /// <param name="AutoArchivingInterval">The interval of scheduled archiving.</param>
		/// <param name="CreateFolder"></param>
		/// <exception cref="System.ArgumentException">Folder does not exists or ArchivingMethod is not ArchivingMethod.None"/> and ArchiveFolder does not exists.</exception>
        /// <exception cref="System.ArgumentNullException">ArchivingMethod is not ArchivingMethod.None"/> and ArchiveFolder does not exists.</exception>
        public LogFolder(String Folder, String ArchiveFolder, ArchivingMethod ArchivingMethod, TimeSpan MaxAllowedAge, TimeSpan AutoArchivingInterval, Boolean CreateFolder)
        {
            mFolder = Path.IsPathRooted(Folder) ? Folder : LogManager.BaseFolder + Folder;

            // Check basic requirements
			if (CreateFolder)
			{
				if (!Directory.Exists(mFolder))
				{
					Directory.CreateDirectory(mFolder);
				}
				if (ArchivingMethod != ArchivingMethod.None)
				{
					if (ArchiveFolder == null)
						throw new ArgumentNullException("ArchiveFolder");

					mArchiveFolder = Path.IsPathRooted(ArchiveFolder) ? ArchiveFolder : LogManager.BaseFolder + ArchiveFolder;

					if (!Directory.Exists(mArchiveFolder))
					{
						Directory.CreateDirectory(mArchiveFolder);
					}
				}
			}
			else
			{
				if (!Directory.Exists(mFolder))
					throw new ArgumentException("Folder must be an existing directory!", "Folder");

				if (ArchivingMethod != ArchivingMethod.None)
				{
					if (ArchiveFolder == null)
						throw new ArgumentNullException("ArchiveFolder");

					mArchiveFolder = Path.IsPathRooted(ArchiveFolder) ? ArchiveFolder : LogManager.BaseFolder + ArchiveFolder;

					if (!Directory.Exists(mArchiveFolder))
						throw new ArgumentException("The archiving path must be an existing directory!", "ArchiveFolder");
				}
			}

            mArchivingMethod = ArchivingMethod;
            mMaxAllowedAge = MaxAllowedAge;

            mAutoArchivingInterval = AutoArchivingInterval;

            // Add event handlers
            mTimer = new System.Threading.Timer(new System.Threading.TimerCallback(ArchivingTimerCallback), this, mAutoArchivingInterval, mAutoArchivingInterval);

            // Immediate archiving
            Archiving();
        }

        #region Archiving helper methods

        /// <summary>
        /// Collects the files appropiate for archiving.
        /// </summary>
        /// <remarks>
        /// A file will be added if it's name conatins *.log* and it aged more than LogFolder.MaxAllowedAge"/>.
        /// </remarks>
        /// <returns>List of files to archive.</returns>
        private List<String> CollectFiles()
        {
            DirectoryInfo di = new DirectoryInfo(mFolder);

            List<String> result = new List<string>();

            foreach (FileSystemInfo fsi in di.GetFileSystemInfos("*.log*")) // Every log files must contain .log somewhere in the nafe of the file
            {
                if (fsi is FileInfo)
                {
                    if (DateTime.Now - fsi.CreationTime > mMaxAllowedAge)
                        result.Add(fsi.FullName);
                }
            }

            return result;
        }

        private const int FILEISOPENED = -2147024864;

        /// <summary>
        /// Archives files using LogFolder.ArchivingMethod method.
        /// </summary>
        /// <param name="Files">List of files to archive.</param>
        private List<String> ArchivingFiles(List<String> Files)
        {
            List<String> archivedfiles = new List<string>();

            switch (mArchivingMethod)
            {
                case ArchivingMethod.Move:

                    #region Move the outdated files

                    foreach (String fp in Files)
                    {
                        String destpath = String.Format("{0}{1}_{2}-{3}-{4}{5}", mArchiveFolder, Path.GetFileNameWithoutExtension(fp), DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, Path.GetExtension(fp));

                        try
                        {
                            File.Move(fp, destpath);
                            archivedfiles.Add(destpath);
                        }
                        catch (FileNotFoundException)
                        {
                            // The source could not be found (perhaps an other archiving timer ambushed it before)
                        }
                        catch (UnauthorizedAccessException)
                        {
                        }
                        catch (IOException e)
                        {
                            if ((int)(typeof(Exception).GetMember("HResult", BindingFlags.NonPublic | BindingFlags.Instance)[0] as PropertyInfo).GetValue(e, new object[] { }) == FILEISOPENED)
                            {
                                // The file is still opened

                                // Suppress file logging
                                EntLibFileTraceListener elftl = LogManager.GetEntLibFileTraceListener(fp);
                                if (elftl != null)
                                {
                                    // The file is locked by one of the trace listeners

                                    elftl.BeginTemporary();

                                    try
                                    {
                                        File.Move(fp, destpath);
                                        archivedfiles.Add(destpath);
                                    }
                                    catch (IOException)
                                    {
                                        // The destination file already exists

                                        File.Move(fp, destpath = String.Format("{0}{1}_{2}{3}", mArchiveFolder, Path.GetFileNameWithoutExtension(destpath), Guid.NewGuid().ToString(), Path.GetExtension(destpath)));
                                        archivedfiles.Add(destpath);
                                    }

                                    elftl.GenerateFilePath();

                                    elftl.EndTemporary();
                                }

                            }
                            else
                            {

                                // The destination file already exists

                                File.Move(fp, destpath = String.Format("{0}{1}_{2}{3}", mArchiveFolder, Path.GetFileNameWithoutExtension(destpath), Guid.NewGuid().ToString(), Path.GetExtension(destpath)));
                                archivedfiles.Add(destpath);
                            }
                        }
                    }

                    #endregion

                    break;
                case ArchivingMethod.Compress:
                    break;
                case ArchivingMethod.CompressTogether:
                    break;
                default:
                    break;
            }

            return archivedfiles;
        }

        /// <summary>
        /// Deletes files.
        /// </summary>
        /// <param name="Files">List of files to delete.</param>
        private void DeleteFiles(List<String> Files)
        {
            foreach (String fp in Files)
            {
                try
                {
                    File.Delete(fp);
                }
                catch (UnauthorizedAccessException)
                {
                    // The file is locked by someone else, we cant do anything
                }
            }
        }

        #endregion

        /// <summary>
        /// Archives the log folder's outdated log files.
        /// </summary>
        /// <remarks>
        /// It can be called exactly or implicitly by letting it to a timer. If one assigned Before and After events those will be called at the appropiate
        /// points of the process. It filters the files in the log folder with *.log* and checks if there are any that exceeded LogManager.MaxAllowedAge or not.
        /// If there are such then it archives them using the LogFolder.ArchivingMethod"/> specified.
        /// </remarks>
        public void Archiving()
        {
            List<String> files = CollectFiles();
            List<String> archivedfiles;

            // Fire Before... actions
            if (BeforeArchiving != null)
                BeforeArchiving(this, files);

            // Archiving files
            archivedfiles = ArchivingFiles(files);

            // Delete files in case of None, Compress, CompressTogether
            if (mArchivingMethod != ArchivingMethod.Move)
            {
                DeleteFiles(files);
            }

            // Fire After... actions
            if (AfterArchiving != null)
                AfterArchiving(this, archivedfiles);
        }

        /// <summary>
        /// Internal timer event handler to call Archiving periodically
        /// </summary>
        /// <param name="State"></param>
        private void ArchivingTimerCallback(Object State)
        {
            Archiving();
        }
    }
}
